// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Concurrent;
using JetBrains.Annotations;

namespace SnapObjects.Data
{
    /// <summary>
    ///     <para>
    ///         Determines the type mapping to use for byte array properties.
    ///     </para>
    ///     <para>
    ///         This type is typically used by database providers (and other extensions). It is generally
    ///         not used in application code.
    ///     </para>
    /// </summary>
    public class ByteArrayTypeMapper : IByteArrayTypeMapper
    {
        private readonly ConcurrentDictionary<int, TypeMapping> _boundedMappings
            = new ConcurrentDictionary<int, TypeMapping>();

        /// <summary>
        ///     Initialized a new instance of the <see cref="ByteArrayTypeMapper" /> class.
        /// </summary>
        /// <param name="maxBoundedLength"> Maximum length of data that can be stored in a byte array property. </param>
        /// <param name="defaultMapping"> Default mapping to be used. </param>
        /// <param name="unboundedMapping"> Mapping to be used for properties with no length specified. </param>
        /// <param name="keyMapping"> Mapping to be used for key properties. </param>
        /// <param name="createBoundedMapping"> Function to create a mapping for a property with a given length. </param>
        public ByteArrayTypeMapper(
            int maxBoundedLength,
            [NotNull] TypeMapping defaultMapping,
            [CanBeNull] TypeMapping unboundedMapping,
            [CanBeNull] TypeMapping keyMapping,
            [NotNull] Func<int, TypeMapping> createBoundedMapping)
        {
            this.MaxBoundedLength = maxBoundedLength;
            this.DefaultMapping = defaultMapping;
            this.UnboundedMapping = unboundedMapping;
            this.KeyMapping = keyMapping;
            this.CreateBoundedMapping = createBoundedMapping;
        }

        /// <summary>
        ///     Gets the maximum length of data that can be stored in a byte array property
        /// </summary>
        public virtual int MaxBoundedLength { get; }

        /// <summary>
        ///     Gets the default mapping to be used.
        /// </summary>
        public virtual TypeMapping DefaultMapping { get; }

        /// <summary>
        ///     Gets the mapping to be used for properties with no length specified
        /// </summary>
        public virtual TypeMapping UnboundedMapping { get; }

        /// <summary>
        ///     Gets the mapping to be used for key properties
        /// </summary>
        public virtual TypeMapping KeyMapping { get; }

        /// <summary>
        ///     Gets a function to create a mapping for a property with a given length.
        /// </summary>
        public virtual Func<int, TypeMapping> CreateBoundedMapping { get; }

        /// <summary>
        ///     Gets the mapping for a property.
        /// </summary>
        /// <param name="keyOrIndex">
        ///     A value indicating whether the property is being used as a key and/or index.
        /// </param>
        /// <param name="size">
        ///     The configured length of the property, or null if it is unbounded.
        /// </param>
        /// <returns> The mapping to be used for the property. </returns>
        public virtual TypeMapping FindMapping(bool keyOrIndex, int? size)
        {
            var defaultMapping = keyOrIndex && this.KeyMapping != null ? this.KeyMapping : this.DefaultMapping;

            if (size.HasValue
                && size != defaultMapping.Size)
            {
                return size <= this.MaxBoundedLength
                    ? _boundedMappings.GetOrAdd(size.Value, this.CreateBoundedMapping)
                    : this.UnboundedMapping;
            }

            return defaultMapping;
        }
    }
}
